#include <Scene.h>

using namespace std;
using namespace cimg_library;

const size_t kMaxReflections = 3;
const float kEpsilon = 0.0001f;

void Scene::render(CImg<float>& bitmap, const size_t height, const size_t width)
{
  float screenLeft = -width / 200.0f;
  float screenRight = width / 200.0f;
  float screenBottom = -height / 200.0f;
  float screenTop = height / 200.0f;

  float dx = (screenRight - screenLeft) / (float)width;
  float dy = (screenTop - screenBottom) / (float)height;

  float rayY = screenBottom;

  for(size_t curRow = 0; curRow < height; curRow++)
  {
    float rayX = screenLeft;

    for(size_t curCol = 0; curCol < width; curCol++)
    {
      Ray emittedRay = emitRay(rayX, rayY);

      RGBColor color;

      rayTrace(emittedRay, color);

      bitmap(curRow, curCol, 0) = color.Red * 255.0f;
      bitmap(curRow, curCol, 1) = color.Green * 255.0f;
      bitmap(curRow, curCol, 2) = color.Blue * 255.0f;

      rayX += dx;
    }
  
    rayY += dy;
  }

}

shared_ptr<Primative> Scene::rayTrace(const Ray& ray, 
                                      RGBColor& color,
                                      size_t reflectionCount)
{
  float distance = 1e20f;
  shared_ptr<Primative> closestPrimative = NULL;
  //float intersection;

  //Find the closest intersection
  for(size_t curPrimative = 0; 
      curPrimative < mPrimatives.size(); 
      curPrimative++)
  {
    float result = mPrimatives[curPrimative]->intersect(ray, distance);

    if(result != 0.0f)
    {
      closestPrimative = mPrimatives[curPrimative];
      //intersection = result;
    }
  }

  if(closestPrimative == NULL)
  {
    return NULL;
  }

  if(closestPrimative->IsLight == true)
  {
    color = closestPrimative->Color;
  }
  else
  {
    Float3 pointOfIntersection = ray.Origin + ray.Direction * distance;
    Float3 n = closestPrimative->getNormal(pointOfIntersection);

    for(size_t curPrimative = 0; 
        curPrimative < mPrimatives.size(); 
        curPrimative++)
    {
      if(mPrimatives[curPrimative]->IsLight == true)
      {
        shared_ptr<Primative>& lightRef = mPrimatives[curPrimative];

        Float3 l = lightRef->getCenter() - pointOfIntersection;

        //color = closestPrimative->Color;

        if(closestPrimative->Diffuse > 0.0f)
        {
          float dot = Float3::Dot(l.normalize(), n);

          if(dot > 0.0f)
          {
            float diff = dot * closestPrimative->Diffuse;
            color += closestPrimative->Color * diff * lightRef->Color;
          }
        }
      }
    }

    Float3 r = ray.Direction - n * 2.0f * Float3::Dot(ray.Direction, n);

    if(reflectionCount < kMaxReflections)
    {
      RGBColor reflectionColor(0.0f, 0.0f, 0.0f);

      rayTrace(Ray(pointOfIntersection + r * kEpsilon, r), 
               reflectionColor,
               reflectionCount + 1);

      color += reflectionColor * 
               closestPrimative->Color *
               closestPrimative->Reflection;

    }
  }

#if 0
  std::cout << "RGB " << color.Red << " " << color.Green << " " <<
    color.Blue << endl;
#endif

  return closestPrimative;
}

void Scene::updateBasis()
{
  mCameraTransform = RotationMatrix(mCameraRotation);
}

Ray Scene::emitRay(float x, float y)
{
  Ray result;
  Float3 rayDirection(x, y, mCameraFocalLength);
  rayDirection = rayDirection.normalize();

  result.Origin = mCameraOrigin;
  result.Direction = mCameraTransform * rayDirection;

  return result;
}
